前面幾天,PokeThreads 的架構已經逐漸長成分散式系統的樣子,但焦點始終放在文字貼文要怎麼被處理跟讀取,現在想加入一個很自然的新功能:讓使用者也能上傳圖片與影片。
新問題馬上出現:這些圖片跟影片應該放在哪裡?
最直覺的做法是跟文字貼文一樣,直接存進 Database,但圖片、影片動輒幾 MB 甚至幾百 MB,塞進 Database 會讓它承擔非常大的 Storage 與 I/O 壓力。
更常見的做法是把檔案本體放進 Object Storage(例如 S3),Database 只保留跟這篇貼文有關的中繼資料:
真正的圖片或影片檔案則另外放在 Object Storage:
Post
├── content
└── media_url
↓
Object Storage
↓
image.jpg
不過馬上又遇到跟 Database 一樣的老問題:如果同一張圖片在短時間內被大量使用者觀看,這些 Request 其實都在重複要求同一份資料。
有沒有辦法讓取得圖片更快,同時也減輕 Object Storage 的負擔?答案就是 CDN。
CDN(Content Delivery Network) 的概念,很像連鎖便利商店的鋪貨邏輯:與其讓每個人都跑一趟遠在外縣市的總倉庫,不如先把常賣的商品鋪到離顧客最近的分店,顧客到附近分店就能買到,不用捨近求遠。
在 CDN 架構裡,我們通常會區分兩個角色:
實際運作起來像這樣:
第一次有人請求圖片:
瀏覽器 -> CDN(Edge)-Cache Miss-> Object Storage(Origin)
CDN 拿到圖片後,順便把它留在 Edge 備用。
之後再有人請求同一張圖片:
瀏覽器 -> CDN(Edge)-Cache Hit-> 直接回傳圖片
這次不需要再跑一趟 Origin。
假設某位使用者上傳一支新影片,Origin 放在美國西岸,如果觀眾分布在歐洲、亞洲各地,每次 Request 都要繞地球一圈才能拿到內容,Latency 自然很高。CDN 讓使用者可以就近從 Edge 取得內容,大幅降低延遲。
同一張圖片如果被大量使用者重複瀏覽,大部分 Request 可以直接由 Edge 的 Cache 回應,Origin 不需要為每一次瀏覽都服務一次,壓力自然小很多。
更重要的是,圖片與影片完全不需要經過 Application Server,這跟 Cache、Database Replication 的精神很像:把不同性質的 Traffic 分流處理。

Application Server 只需要專心處理這些事:
GET /feed
POST /posts
POST /follow
媒體檔案的傳輸則完全交給 CDN 與 Object Storage。
實務上更進一步的做法,是讓 Client 直接把檔案上傳到 Object Storage,Application Server 只負責驗證與授權,真正的檔案傳輸完全不經過它,藉此大幅降低 Server 的 Network Load。
不過跟 Cache 一樣,CDN 也會遇到「使用者可能拿到舊資料」的問題,一樣需要設定 TTL,決定檔案在 CDN 上該保留多久。
CDN 不是免費資源,計費通常跟流量、Request 數量、地區、快取命中率有關,實際計價方式依供應商而定,常見的有 Cloudflare、Akamai、Amazon CloudFront、Google Cloud CDN 等。
CDN 能降低 Origin 的負擔,但如果每支影片都是幾百 MB,網路傳輸量還是很可觀。
CDN 並不能解決「檔案太大」這件事,它只是讓傳輸路徑更有效率。真正要控制成本,得從資料本身下手:
核心概念很單純:少傳一點資料,通常比想辦法傳更多資料還要划算。
CDN 適合存放的是不太會變、大家都在看的靜態內容:
不適合放的則包括:
PokeThreads 的 Feed 本身是高度個人化的,例如 Pikachu 跟 Charmander 看到的內容通常不一樣,所以整份 Feed 不適合直接放進 CDN,但 Feed 裡某一篇貼文附帶的圖片,還是可以單獨放進 CDN。
另外,如果某個檔案幾乎只會被請求一次,放進 CDN 帶來的效益也很有限。
Purge CDN Cache 是指手動或透過程式,強制要求 CDN 的 Edge 節點刪除舊檔案,常見時機包括:
不過大量執行 Purge 可能引發連鎖反應:
大量 Cache Miss
↓
大量 Origin Requests
↓
Origin Traffic 上升
↓
成本也跟著上升
有些供應商甚至會針對頻繁的 Purge 指令額外收費,所以更實用的做法是使用版本化 URL:
不要用固定的 image.jpg,而是附帶版本號的 image-a83f12.jpg,檔案更新後產生新的網址 image-b72c91.jpg。CDN 會把它們視為不同資源,舊資源自然留在 Cache,新資源則用新網址提供,不需要特地清除舊的。
加入 CDN 之後,整體架構變成:

CDN 幫我們處理了:
但也帶來新的課題要考慮:TTL、大檔案優化、Purge 策略、CDN 成本。
不過今天其實跳過了一個環節:使用者上傳的原始檔案,到真正能被 CDN 快取的圖片之間,中間到底發生了什麼事?一張使用者上傳的 20MB 原始照片,總不會原封不動就拿去讓全世界的人下載吧?這是明天要處理的問題。